#include "src/net/channel.h"

#include <assert.h>
#include <poll.h>

#include "src/base/fio66_logger.h"
#include "src/net/event_loop.h"

namespace fio66::net
{
 const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = POLLIN | POLLPRI;
const int Channel::kWriteEvent = POLLOUT;

Channel::Channel(EventLoop* loop, int fd__)
  : loop_(loop),
    fd_(fd__),
    events_(0),
    revents_(0),
    index_(-1),
    logHup_(true),
    tied_(false),
    event_handling_(false),
    added_to_loop_(false)
{
}

Channel::~Channel()
{
  assert(!event_handling_);
  assert(!added_to_loop_);
  if (loop_->IsInLoopThread())
  {
    assert(!loop_->HasChannel(this));
  }
}

void Channel::Tie(const std::shared_ptr<void>& obj)
{
  tie_ = obj;
  tied_ = true;
}

void Channel::Update()
{
  added_to_loop_ = true;
  loop_->UpdateChannel(this);
}

void Channel::Remove()
{
  assert(IsNoneEvent());
  added_to_loop_ = false;
  loop_->RemoveChannel(this);
}

void Channel::HandleEvent(Timestamp receiveTime)
{
  std::shared_ptr<void> guard;
  if (tied_)
  {
    guard = tie_.lock();
    if (guard)
    {
      HandleEventWithGuard(receiveTime);
    }
  }
  else
  {
    HandleEventWithGuard(receiveTime);
  }
}

void Channel::HandleEventWithGuard(Timestamp receiveTime)
{
  event_handling_ = true;
  LOGI << ReventsToString();
  if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
  {
    if (logHup_)
    {
      LOGW << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
    }
    if (close_callback_) 
    {
      close_callback_();
    } 
  }

  if (revents_ & POLLNVAL)
  {
    LOGW << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
  }

  if (revents_ & (POLLERR | POLLNVAL))
  {
    if (error_callback_) {
      error_callback_();
    }
  }
  if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (read_callback_) {
      read_callback_(receiveTime);
    }
  }
  if (revents_ & POLLOUT)
  {
    if (write_callback_) {
      write_callback_();
    }
  }
  event_handling_ = false;
}

std::string Channel::ReventsToString() const
{
  return EventsToString(fd_, revents_);
}

std::string Channel::EventsToString() const
{
  return EventsToString(fd_, events_);
}

std::string Channel::EventsToString(int fd, int ev)
{
  std::ostringstream oss;
  oss << fd << ": ";
  if (ev & POLLIN)
    oss << "IN ";
  if (ev & POLLPRI)
    oss << "PRI ";
  if (ev & POLLOUT)
    oss << "OUT ";
  if (ev & POLLHUP)
    oss << "HUP ";
  if (ev & POLLRDHUP)
    oss << "RDHUP ";
  if (ev & POLLERR)
    oss << "ERR ";
  if (ev & POLLNVAL)
    oss << "NVAL ";

  return oss.str();
} 


} // namespace fio66::net
